<template>
  <div
    class="slider__runway"
    :class="{ 'disabled': sliderDisabled, 'undraggable': undraggable, 'is-range': range }"
    :style="runwayStyle"
    @click="onSliderClick"
    ref="slider">
    <template v-if="!range">
      <div class="slider-before__bar" :style="beforeBarStyle"></div>
      <div class="slider-after__bar" :style="afterBarStyle"></div>
    </template>

    <div v-else class="el-slider__bar" :style="barStyle"></div>

    <slider-button
      :vertical="vertical"
      v-model="firstValue"
      :undraggable="undraggable"
      :is-pause="isPause"
      :tooltip-class="tooltipClass"
      ref="sliderButton1Ref">
    </slider-button>

    <slider-button
      :vertical="vertical"
      v-model="secondValue"
      :undraggable="undraggable"
      :tooltip-class="tooltipClass"
      ref="sliderButton2Ref"
      v-if="range">
    </slider-button>

    <template v-if="showStops">
      <div class="slider__stop" v-for="(item, key) in stops" :key="key" :style="getStopStyle(item)"></div>
    </template>

    <template v-if="markList.length > 0">
      <div>
        <div
          v-for="(item, key) in markList"
          :style="getStopStyle(item.position)"
          class="slider__stop slider__marks-stop"
          :key="key"
        ></div>
      </div>

      <div class="slider__marks">
        <slider-marker
          :mark="item.mark" v-for="(item, key) in markList"
          :key="key"
          :style="getStopStyle(item.position)"
        ></slider-marker>
      </div>
    </template>
  </div>
</template>

<script>
import SliderButton from './slider-button.vue';
import SliderMarker from './marker';
import Emitter from '../mixins/emitter';

export default {
  name: 'TimelineSlider',
  mixins: [Emitter],
  props: {
    min: {
      type: Number,
      default: 0,
    },
    max: {
      type: Number,
      default: 100,
    },
    step: {
      type: Number,
      default: 1,
    },
    value: {
      type: [Number, Array],
      default: 0,
    },
    // 是否保持绘制的时间线，如果为 true, 则拖动滑动按钮，不绘制线，需要指定一个 currentPosition
    isKeepLine: {
      type: Boolean,
      default: false,
    },
    // 当前位置
    currentPosition: {
      type: Number,
      default: 0,
    },
    showStops: {
      type: Boolean,
      default: false,
    },
    showTooltip: {
      type: Boolean,
      default: true,
    },
    formatTooltip: Function,
    disabled: {
      type: Boolean,
      default: false,
    },
    range: {
      type: Boolean,
      default: false,
    },
    vertical: {
      type: Boolean,
      default: false,
    },
    height: {
      type: String,
    },
    label: {
      type: String,
    },
    tooltipClass: String,
    marks: Object,
    // 不可拖动的
    undraggable: {
      type: Boolean,
      default: false,
    },
    isPause: {
      type: Boolean,
      default: false,
    },
  },

  components: {
    SliderButton,
    SliderMarker,
  },

  data() {
    return {
      firstValue: null,
      secondValue: null,
      oldValue: null,
      dragging: false,
      sliderSize: 1,
    };
  },

  watch: {
    value(val, oldVal) {
      if (this.dragging ||
        Array.isArray(val) &&
        Array.isArray(oldVal) &&
        val.every((item, index) => item === oldVal[index])) {
        return;
      }
      this.setValues();
    },

    dragging(val) {
      if (!val) {
        this.setValues();
      }
    },

    firstValue(val) {
      if (this.range) {
        this.$emit('input', [this.minValue, this.maxValue]);
      } else {
        this.$emit('input', val);
      }
    },

    secondValue() {
      if (this.range) {
        this.$emit('input', [this.minValue, this.maxValue]);
      }
    },

    min() {
      this.setValues();
    },

    max() {
      this.setValues();
    },
  },

  methods: {
    valueChanged() {
      if (this.range) {
        return ![this.minValue, this.maxValue].every((item, index) => item === this.oldValue[index]);
      } else {
        return this.value !== this.oldValue;
      }
    },
    setValues() {
      if (this.min > this.max) {
        console.error('[Element Error][Slider]min should not be greater than max.');
        return;
      }
      const val = this.value;
      if (this.range && Array.isArray(val)) {
        if (val[1] < this.min) {
          this.$emit('input', [this.min, this.min]);
        } else if (val[0] > this.max) {
          this.$emit('input', [this.max, this.max]);
        } else if (val[0] < this.min) {
          this.$emit('input', [this.min, val[1]]);
        } else if (val[1] > this.max) {
          this.$emit('input', [val[0], this.max]);
        } else {
          this.firstValue = val[0];
          this.secondValue = val[1];
          if (this.valueChanged()) {
            // this.dispatch('ElFormItem', 'el.form.change', [this.minValue, this.maxValue]);
            this.oldValue = val.slice();
          }
        }
      } else if (!this.range && typeof val === 'number' && !isNaN(val)) {
        if (val < this.min) {
          this.$emit('input', this.min);
        } else if (val > this.max) {
          this.$emit('input', this.max);
        } else {
          this.firstValue = val;
          if (this.valueChanged()) {
            // this.dispatch('ElFormItem', 'el.form.change', val);
            this.oldValue = val;
          }
        }
      }
    },

    setPosition(percent) {
      const targetValue = this.min + percent * (this.max - this.min) / 100;
      if (!this.range) {
        this.$refs.sliderButton1Ref.setPosition(percent);
        return;
      }
      let button;
      if (Math.abs(this.minValue - targetValue) < Math.abs(this.maxValue - targetValue)) {
        button = this.firstValue < this.secondValue ? 'sliderButton1Ref' : 'sliderButton2Ref';
      } else {
        button = this.firstValue > this.secondValue ? 'sliderButton1Ref' : 'sliderButton2Ref';
      }
      this.$refs[button].setPosition(percent);
    },

    onSliderClick(event) {
      if (this.sliderDisabled || this.dragging || this.undraggable) return;
      this.resetSize();
      if (this.vertical) {
        const sliderOffsetBottom = this.$refs.slider.getBoundingClientRect().bottom;
        this.setPosition((sliderOffsetBottom - event.clientY) / this.sliderSize * 100);
      } else {
        const sliderOffsetLeft = this.$refs.slider.getBoundingClientRect().left;
        this.setPosition((event.clientX - sliderOffsetLeft) / this.sliderSize * 100);
      }
      this.emitChange();
    },

    resetSize() {
      if (this.$refs.slider) {
        this.sliderSize = this.$refs.slider[`client${this.vertical ? 'Height' : 'Width'}`];
      }
    },

    emitChange() {
      this.$nextTick(() => {
        this.$emit('change', this.range ? [this.minValue, this.maxValue] : this.value);
      });
    },

    getStopStyle(position) {
      return this.vertical ? { 'bottom': position + '%' } : { 'left': position + '%' };
    },
  },

  computed: {
    stops() {
      if (!this.showStops || this.min > this.max) return [];
      if (this.step === 0) {
        process.env.NODE_ENV !== 'production' &&
        console.warn('[Element Warn][Slider]step should not be 0.');
        return [];
      }
      const stopCount = (this.max - this.min) / this.step;
      const stepWidth = 100 * this.step / (this.max - this.min);
      const result = [];
      for (let i = 1; i < stopCount; i++) {
        result.push(i * stepWidth);
      }

      if (this.range) {
        return result.filter(step => {
          return step < 100 * (this.minValue - this.min) / (this.max - this.min) ||
            step > 100 * (this.maxValue - this.min) / (this.max - this.min);
        });
      } else {
        return result.filter(step => step > 100 * (this.firstValue - this.min) / (this.max - this.min));
      }
    },

    markList() {
      if (!this.marks) {
        return [];
      }

      const marksKeys = Object.keys(this.marks);
      return marksKeys.map(parseFloat)
        .sort((a, b) => a - b)
        .filter(point => point <= this.max && point >= this.min)
        .map(point => ({
          point,
          position: (point - this.min) * 100 / (this.max - this.min),
          mark: this.marks[point],
        }));
    },

    minValue() {
      return Math.min(this.firstValue, this.secondValue);
    },

    maxValue() {
      return Math.max(this.firstValue, this.secondValue);
    },

    barSize() {
      return this.range
        ? `${ 100 * (this.maxValue - this.minValue) / (this.max - this.min) }%`
        : `${ 100 * (this.firstValue - this.min) / (this.max - this.min) }%`;
    },

    barStart() {
      return this.range
        ? `${ 100 * (this.minValue - this.min) / (this.max - this.min) }%`
        : '0%';
    },

    beforeBarSize() {
      if (this.isKeepLine) {
        return `${100 * (this.currentPosition - this.min) / (this.max - this.min)}%`;
      }

      if (this.firstValue > this.currentPosition) {
        return `${100 * (this.currentPosition - this.min) / (this.max - this.min)}%`;
      }
      return `${ 100 * (this.firstValue - this.min) / (this.max - this.min) }%`;
    },

    beforeBarStart() {
      return '0%';
    },

    afterBarSize() {
      if (this.isKeepLine) {
        return `${100 * ((this.max - (this.currentPosition + 1)) - this.min) / (this.max - this.min)}%`;
      }

      if (this.firstValue > this.currentPosition) {
        return `${100 * ((this.firstValue - this.currentPosition) - this.min) / (this.max - this.min)}%`;
      }

      return '0%';
    },

    afterBarStart() {
      return `${100 * ((this.currentPosition + 1) - this.min) / (this.max - this.min)}%`;
    },

    precision() {
      let precisions = [this.min, this.max, this.step].map(item => {
        let decimal = ('' + item).split('.')[1];
        return decimal ? decimal.length : 0;
      });
      return Math.max.apply(null, precisions);
    },

    runwayStyle() {
      return this.vertical ? { height: this.height } : {};
    },

    barStyle() {
      return this.vertical
        ? {
          height: this.barSize,
          bottom: this.barStart,
        } : {
          width: this.barSize,
          left: this.barStart,
        };
    },

    beforeBarStyle() {
      return this.vertical
        ? {
          height: this.beforeBarSize,
          bottom: this.beforeBarStart,
        } : {
          width: this.beforeBarSize,
          left: this.beforeBarStart,
        };
    },

    afterBarStyle() {
      return this.vertical
        ? {
          height: this.afterBarSize,
          bottom: this.afterBarStart,
        } : {
          width: this.afterBarSize,
          left: this.afterBarStart,
        };
    },

    sliderDisabled() {
      return this.disabled || (this.elForm || {}).disabled;
    },
  },

  mounted() {
    let valuetext;

    if (this.range) {
      if (Array.isArray(this.value)) {
        this.firstValue = Math.max(this.min, this.value[0]);
        this.secondValue = Math.min(this.max, this.value[1]);
      } else {
        this.firstValue = this.min;
        this.secondValue = this.max;
      }
      this.oldValue = [this.firstValue, this.secondValue];
      valuetext = `${this.firstValue}-${this.secondValue}`;
    } else {
      if (typeof this.value !== 'number' || isNaN(this.value)) {
        this.firstValue = this.min;
      } else {
        this.firstValue = Math.min(this.max, Math.max(this.min, this.value));
      }
      this.oldValue = this.firstValue;
      valuetext = this.firstValue;
    }

    this.$el.setAttribute('aria-valuetext', valuetext);

    // label screen reader
    this.$el.setAttribute('aria-label', this.label ? this.label : `slider between ${this.min} and ${this.max}`);

    this.resetSize();
    window.addEventListener('resize', this.resetSize);
  },

  beforeDestroy() {
    window.removeEventListener('resize', this.resetSize);
  },
};
</script>

<style lang="scss" scoped>
.slider__runway {
  width: 100%;
  height: 6px;
  margin-top: 8px;
  margin-bottom: 16px;
  background-color: #335374;
  border-radius: 3px;
  position: relative;
  z-index: 1;
  cursor: pointer;
  vertical-align: middle;

  &.undraggable {
    cursor: default;
  }

  &:before {
    content: "";
    position: absolute;
    background: linear-gradient(90deg, rgba(0, 186, 255, 0.3) 0%, rgba(30, 126, 172, 1) 100%);
    width: 104px;
    height: 6px;
    left: -104px;
    top: 0;
    z-index: -1;
  }

  &:after {
    content: "";
    position: absolute;
    background: linear-gradient(90deg, rgba(199, 132, 57, 0.8) 0%, rgba(255, 162, 60, 0.3) 100%);
    width: 104px;
    height: 6px;
    right: -104px;
    top: 0;
    z-index: -1;
  }

  &.is-range {
    background-color: rgba(0, 186, 255, 0.3);

    &:before {
      background: rgba(0, 186, 255, 0.3);
    }

    &:after {
      background: rgba(0, 186, 255, 0.3);
    }
  }
}

.el-slider__bar {
  position: absolute;
  height: 6px;
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
  background-color: #03C7FF;
}

.slider-before__bar,
.slider-after__bar {
  position: absolute;
  height: 6px;
  border-top-left-radius: 3px;
  border-bottom-left-radius: 3px;
}

.slider-before__bar {
  background: linear-gradient(90deg, rgba(30, 126, 172, 1) 0%, #03C7FF 100%);
}

.slider-after__bar {
  background: linear-gradient(90deg, #FFA23C 0%, rgba(199, 132, 57, 0.8) 100%);
}

.slider__stop {
  height: 6px;
  width: 6px;
  background-color: #00FCFF;
  border-radius: 100%;
  transform: translateX(-50%);
  position: absolute;
}

.slider__marks {
  top: 0;
  left: 12px;
  width: 18px;
  height: 100%;

  .slider__marks-text {
    position: absolute;
    transform: translateX(-50%);
    font-size: 14px;
    color: #909399;
    margin-top: 15px;
  }
}
</style>
